# 第7章 生产环境配置

# 环境配置的封装

// package.json
"scripts": {
    "dev": "ENV=development webpack-dev-server",
    "build": "ENV=production webpack"
}

// webpack.config.js
const ENV = process.env.ENV;
1
2
3
4
5
6
7
8
// package.json
"scripts": {
    "dev": "webpack-dev-server --config=webpack.development.config.js",
    "build": "webpack --config=webpack.production.config.js"
}
1
2
3
4
5

# 开启production模式

# 环境变量

// webpack.config.js
const webpack = require('webpack');
module.exports = {
    entry: './app.js',
    +  mode: 'production',
    -  plugins: [
    -    new UglifyJsPlugin(/* ... */),
    -    new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("production") }),
    -    new webpack.optimize.ModuleConcatenationPlugin(),
    -    new webpack.NoEmitOnErrorsPlugin()
    -  ]
}

// app.js
console.log(ENV)// production
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

DefinePlugin在替换环境变量时对于字符串类型的值进行的是完全替换。不添加JSON.stringify,在替换后就会成为变量名,而非字符串值。所以字符串或者包含字符串的对象都要加上JSON.stringify。

new webpack.DefinePlugin({
    ccc: JSON.stringify({
        a: 'abc'
    })
})
1
2
3
4
5

webpack4中如果启用了mode: production,则已经设置好process.env.NODE_ENV=production。

# source map

source map指的是将编译、打包、压缩后的代码映射回源代码的过程。
我们启动了devtool配置项,source map就会跟随源代码一步步被传递,直到生成最后的map文件。bundle.js.map。
map文件只会在浏览器打开开发者工具时被加载。所以对用户使用是没有任何影响的,但是任何人都可以看到源码。

# 配置

对于js

module.exports = {
    devtool: 'source-map'
}
1
2
3

对于CSS、SCSS、LESS来说,还需要:

devtool: 'source-map',
module: {
    rules: [
        {
            test: /\.scss$/,
            use: [
                'style-loader',
                {
                    loader: 'css-loader',
                    options: {
                        sourceMap: true
                    }
                },
                {
                    loader: 'sass-loader',
                    options: {
                        sourceMap: true
                    }
                },
            ]
        }
    ]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

之后就可以在开发者工具“Sources”下的“webpack://”目录中找到工程源码。

生成完整source map会延长整体构建时间,如果对打包速度需求比较高的话,可以选择一个简化版的source map。cheap-module-eval-source-map属于打包速度和源码信息还原程度的一个良好的折中。

hidden-source-mapwebpack仍然会产出完整的map文件,只不过不会在bundle文件中添加对于map文件的引用。
nosources-source-map打包部署后可以在Sources选项卡中看到源码的目录结构,但是文件的具体内容会被隐藏起来。

生成环境中还可以使用source map,然后通过设置nginx将.map文件只对固定的白名单(公司内网开发),这样用户看不到源码,我们能看到源码。

# 资源压缩

# 压缩JavaScript

webpack4中默认使用了terser的插件terser-webpack-plugin。

webpack3中需要调用webpack.optimize.UglifyJsPlugin。

terser-webpack-plugin支持ES6+代码的压缩。

// webpack version < 4
plugins: [new webpack.optimize.UglifyJsPlugin()]
1
2

webpack4之后,这项配置被移到了config.optimization.minimize。如果mode:production则不需要人为设置。

module.exports = {
    optimization: {
        minimize: true
    }
}
1
2
3
4
5
const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
    optimization: {
        minimizer: [
            new TerserPlugin({
                test: /\.js$/,
                cache: true,
                parallel: true, // 多个进程进行压缩
            })
        ]
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

version4: https://v4.webpack.docschina.org/plugins/terser-webpack-plugin

version5: https://webpack.js.org/plugins/terser-webpack-plugin/

# 压缩CSS

压缩CSS文件前提是使用extract-text-webpack-plugin或mini-css-extract-plugin将样式提取出来,接着使用optimize-css-assets-webpack-plugin来进行压缩。

const ExtractTextPlugin = require('extract-text-webpack-plugin');
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
module.exports = {
    module: {
        rules: [
            {
                test: /\.css$/,
                use: ExtractTextPlugin.extract({
                    fallback: 'style-loader',
                    use: 'css-loader'
                })
            }
        ]
    },
    plugins: [new ExtractTextPlugin('style.css')],
    optimization: {
        minimizer: [
            new OptimizeCSSAssetsPlugin({
                // 压缩处理器的配置
                cssProcessorOptions: {
                    discardComments: {
                        removeAll: true
                    }
                },
                // 是否展示log
                canPrint: true
            })
        ]
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

# 缓存

# 资源hash

当代码发生变化时相对应的hash也会发生变化。

output: {
    filename: 'bundle@[chunkhash].js'
}
1
2
3

# 输出动态HTML

解决了每次更改后修改资源引用路径的困难。使用了html-webpack-plugin

const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
    plugins: [
        new HtmlWebpackPlugin();
    ]
}
1
2
3
4
5
6

# 使chunk id更稳定

output: {
    filename: '[name]@[chunkhash].js'
},
plugins: [
    new webpack.optimize.CommonsChunkPlugin({
        name: 'vendor'
    })
]
1
2
3
4
5
6
7
8

新模块插入进来时就会导致其他模块的chunk hash发生变化,静态文件文件名会变化,会导致客户端重新下载整个文件。

解决方法webpack3中内部自带了HashedModuleIdsPlugin,它可以为每个模块按照其所在路径生成一个字符串类型的hash id。

plugins: [
    new webpack.HashedModuleIdsPlugin(),
    new webpack.optimize.CommonsChunkPlugin({
        name: 'vendor'
    })
]
1
2
3
4
5
6

webpack3以下的版本可以使用webpack-hashed-module-id-plugin插件。

webpack4及以上已经修改了模块id的生成机制,不再会有该问题。

# bundle体积监控和分析

const Analyzer = require('webpack.bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
    plugins: [
        new Analyzer();
    ]
}
1
2
3
4
5
6

项目启动后就会生成一张bundle的模块组成结构图。